package com.wmx.thymeleafapp.utils;

import cn.hutool.core.date.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 高级重复文件检测工具类
 * 特性：多级校验（大小+采样哈希+全量哈希）、并行计算、无效文件过滤
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2025/9/7 下午12:26
 */
public class AdvancedDuplicateFileDetector {
    private static final Logger log = LoggerFactory.getLogger(AdvancedDuplicateFileDetector.class);

    // 配置参数（可根据需求调整）
    private static final int CPU_CORES = Runtime.getRuntime().availableProcessors(); // CPU核心数（用于线程池）
    private static final int SAMPLE_BLOCK_SIZE = 1024; // 采样块大小（1KB）
    private static final int SAMPLE_BLOCK_COUNT = 3; // 采样块数量（开头+中间+结尾）
    private static final String SAMPLE_HASH_ALG = "CRC32"; // 采样哈希算法（快）
    private static final String FULL_HASH_ALG = "MD5"; // 全量哈希算法（平衡速度与准确性）
    private static final boolean FILTER_HIDDEN_FILES = true; // 是否过滤隐藏文件
    private static final boolean FILTER_SYMLINKS = true; // 是否过滤符号链接


    // ------------------------------ 测试方法 ------------------------------
    public static void main(String[] args) throws IOException, InterruptedException {
        File homeDirectory = FileSystemView.getFileSystemView().getHomeDirectory();
        // String dirPath = homeDirectory.getAbsolutePath();
        String dirPath = "D:\\xwechat_files\\wxid_tdyk16sxks3b22_cc96\\msg\\migrate\\Video";

        AdvancedDuplicateFileDetector detector = new AdvancedDuplicateFileDetector();
        Map<String, List<File>> duplicates = detector.detectDuplicateFiles(dirPath);

        // 输出结果
        if (duplicates.isEmpty()) {
            System.out.println("未发现重复文件");
            return;
        }
        System.out.printf("共发现%d组重复文件：%n", duplicates.size());
        for (Map.Entry<String, List<File>> entry : duplicates.entrySet()) {
            System.out.printf("全量哈希（%s）：%s%n", FULL_HASH_ALG, entry.getKey());
            System.out.println("重复文件路径：");
            entry.getValue().forEach(path -> System.out.println("  - " + path.getAbsolutePath() + " " + DateUtil.date(path.lastModified())));
            // deleteDuplicateFiles(entry.getValue());
            System.out.println("----------------------------------------");
        }
    }

    /**
     * 删除重复文件
     *
     * @param duplicateFiles ：重复文件
     * @throws IOException /
     */
    private static void deleteDuplicateFiles(List<File> duplicateFiles) throws IOException {
        boolean isFirst = true;
        for (File file : duplicateFiles) {
            if (!isFirst) {
                Files.deleteIfExists(file.toPath());
                System.out.println("    - 删除文件:" + file.getPath());
            }
            isFirst = false;
        }
    }

    /**
     * 检测指定目录下的重复文件（多级校验+并行计算）
     *
     * @param dirPath 待检测目录路径
     * @return 重复文件映射：key=全量哈希，value=文件绝对路径列表（按字典序排序）
     * @throws IOException              目录遍历/文件读取错误
     * @throws InterruptedException     线程池等待中断
     */
    public Map<String, List<File>> detectDuplicateFiles(String dirPath) throws IOException, InterruptedException {
        File dirFile = new File(dirPath);
        validateDirectory(dirFile);

        // 1. 流式遍历目录，收集有效文件（过滤隐藏文件、符号链接）
        List<File> validFiles = listValidFiles(dirFile);
        log.info("目录[{}]下共发现{}个有效文件（已过滤无效文件）", dirPath, validFiles.size());
        if (validFiles.isEmpty()) return Collections.emptyMap();

        // 2. 第一级：按文件大小分组（过滤不同大小的文件）
        Map<Long, List<File>> filesBySize = validFiles.stream()
                .collect(Collectors.groupingBy(File::length));
        log.debug("按大小分组后，得到{}个不同大小的文件组. CPU_CORES={}", filesBySize.size(), CPU_CORES);

        // 3. 并行处理每个大小组（核心优化：利用多核CPU）
        ExecutorService executor = Executors.newFixedThreadPool(CPU_CORES);
        Map<String, List<File>> duplicateResult = new TreeMap<>(); // 线程安全？非线程安全，需同步
        Object lock = new Object(); // 用于结果集同步

        for (Map.Entry<Long, List<File>> sizeGroup : filesBySize.entrySet()) {
            List<File> sameSizeFiles = sizeGroup.getValue();
            if (sameSizeFiles.size() <= 1) continue; // 单个文件组跳过

            // 提交线程任务：处理当前大小组的多级校验
            executor.submit(() -> {
                try {
                    // 3.1 第二级：采样哈希分组（过滤采样不同的文件）
                    Map<String, List<File>> filesBySampleHash = groupBySampleHash(sameSizeFiles);
                    log.debug("大小[{}KB]的文件组，按采样哈希分组后得到{}个组",
                            sizeGroup.getKey() / 1024, filesBySampleHash.size());

                    // 3.2 第三级：全量哈希分组（最终确认重复文件）
                    Map<String, List<File>> sizeGroupResult = groupByFullHash(filesBySampleHash);

                    // 同步写入结果集（避免线程安全问题）
                    synchronized (lock) {
                        for (Map.Entry<String, List<File>> entry : sizeGroupResult.entrySet()) {
                            duplicateResult.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).addAll(entry.getValue());
                        }
                    }
                } catch (Exception e) {
                    log.error("处理大小组[{}KB]时出错", sizeGroup.getKey() / 1024, e);
                }
            });
        }

        // 等待所有线程完成，关闭线程池
        executor.shutdown();
        if (!executor.awaitTermination(1, TimeUnit.HOURS)) { // 超时1小时（可调整）
            executor.shutdownNow();
            log.warn("检测超时，强制终止未完成任务");
        }

        // 4. 过滤非重复组（仅保留2个以上文件的组）+ 路径排序
        filterNonDuplicateGroups(duplicateResult);
        sortFilePaths(duplicateResult);
        log.info("重复文件检测完成，共发现{}组重复文件", duplicateResult.size());

        return duplicateResult;
    }

    // ------------------------------ 核心辅助方法 ------------------------------

    /**
     * 流式遍历目录，收集有效文件（过滤隐藏文件、符号链接）
     */
    private List<File> listValidFiles(File dir) {
        List<File> validFiles = new ArrayList<>();
        Queue<File> dirQueue = new LinkedList<>();
        dirQueue.add(dir);

        while (!dirQueue.isEmpty()) {
            File currentDir = dirQueue.poll();
            File[] children = currentDir.listFiles();
            if (children == null) {
                log.warn("无法读取目录[{}]的内容（可能权限不足）", currentDir.getAbsolutePath());
                continue;
            }

            for (File child : children) {
                if (child.isDirectory()) {
                    dirQueue.add(child); // 递归处理子目录
                    continue;
                }

                // 过滤无效文件
                if (isInvalidFile(child)) {
                    log.debug("跳过无效文件[{}]", child.getAbsolutePath());
                    continue;
                }

                validFiles.add(child);
            }
        }
        return validFiles;
    }

    /**
     * 判断文件是否为无效文件（隐藏文件、符号链接）
     */
    private boolean isInvalidFile(File file) {
        if (FILTER_HIDDEN_FILES && file.isHidden()) return true;
        if (FILTER_SYMLINKS && isSymbolicLink(file)) return true;
        return !file.isFile() || !file.exists();
    }

    /**
     * 判断文件是否为符号链接（兼容Windows/Linux）
     */
    private boolean isSymbolicLink(File file) {
        if (file == null || !file.exists()) return false;
        try {
            File canonicalFile = file.getCanonicalFile();
            File absoluteFile = file.getAbsoluteFile();
            // 符号链接的canonicalPath与absolutePath不同
            return !canonicalFile.getPath().equals(absoluteFile.getPath());
        } catch (IOException e) {
            log.error("判断文件[{}]是否为符号链接时出错", file.getAbsolutePath(), e);
            return false;
        }
    }

    /**
     * 按采样哈希分组（第二级校验）
     */
    private Map<String, List<File>> groupBySampleHash(List<File> files) throws IOException, NoSuchAlgorithmException {
        Map<String, List<File>> groupMap = new HashMap<>();
        for (File file : files) {
            String sampleHash = calculateSampleHash(file);
            groupMap.computeIfAbsent(sampleHash, k -> new ArrayList<>()).add(file);
        }
        return groupMap;
    }

    /**
     * 计算文件的采样哈希（读开头+中间+结尾片段）
     */
    private String calculateSampleHash(File file) throws IOException, NoSuchAlgorithmException {
        long fileSize = file.length();
        if (fileSize == 0) return "EMPTY_FILE_SAMPLE_HASH"; // 空文件特殊处理

        try (FileInputStream fis = new FileInputStream(file);
             FileChannel channel = fis.getChannel()) {

            MessageDigest digest = MessageDigest.getInstance(FULL_HASH_ALG); // 用MD5做采样哈希（简单通用）
            ByteBuffer buffer = ByteBuffer.allocate(SAMPLE_BLOCK_SIZE);

            // 1. 读取开头片段
            readBlock(channel, 0, buffer, digest);

            // 2. 读取中间片段（仅当文件大于2个采样块时）
            if (fileSize > 2 * SAMPLE_BLOCK_SIZE) {
                long middlePos = (fileSize - SAMPLE_BLOCK_SIZE) / 2;
                readBlock(channel, middlePos, buffer, digest);
            }

            // 3. 读取结尾片段（仅当文件大于1个采样块时）
            if (fileSize > SAMPLE_BLOCK_SIZE) {
                long endPos = fileSize - SAMPLE_BLOCK_SIZE;
                readBlock(channel, endPos, buffer, digest);
            }

            // 采样哈希转16进制
            return bytesToHex(digest.digest());
        }
    }

    /**
     * 读取文件指定位置的块，并更新哈希
     */
    private void readBlock(FileChannel channel, long pos, ByteBuffer buffer, MessageDigest digest) throws IOException {
        buffer.clear();
        channel.position(pos);
        int bytesRead = channel.read(buffer);
        if (bytesRead > 0) {
            buffer.flip();
            digest.update(buffer);
        }
    }

    /**
     * 按全量哈希分组（第三级校验）
     */
    private Map<String, List<File>> groupByFullHash(Map<String, List<File>> filesBySampleHash)
            throws IOException, NoSuchAlgorithmException {
        Map<String, List<File>> groupMap = new HashMap<>();
        for (List<File> sampleGroup : filesBySampleHash.values()) {
            if (sampleGroup.size() <= 1) continue; // 采样相同但仅1个文件，跳过

            for (File file : sampleGroup) {
                String fullHash = calculateFullHash(file);
                groupMap.computeIfAbsent(fullHash, k -> new ArrayList<>()).add(file);
            }
        }
        return groupMap;
    }

    /**
     * 计算文件的全量哈希（NIO内存映射，高效处理大文件）
     */
    private String calculateFullHash(File file) throws IOException, NoSuchAlgorithmException {
        try (FileInputStream fis = new FileInputStream(file);
             FileChannel channel = fis.getChannel()) {

            MessageDigest digest = MessageDigest.getInstance(FULL_HASH_ALG);
            long fileSize = file.length();
            long pos = 0;
            final int BUFFER_SIZE = 8 * 1024 * 1024; // 8MB内存映射块（平衡内存与效率）

            while (pos < fileSize) {
                long blockSize = Math.min(BUFFER_SIZE, fileSize - pos);
                ByteBuffer mappedBuffer = channel.map(FileChannel.MapMode.READ_ONLY, pos, blockSize);
                digest.update(mappedBuffer);
                pos += blockSize;
            }

            return bytesToHex(digest.digest());
        }
    }

    // ------------------------------ 通用工具方法 ------------------------------

    /**
     * 校验目录合法性
     */
    private void validateDirectory(File dir) {
        if (dir == null) throw new IllegalArgumentException("目录不能为null");
        if (!dir.exists()) throw new IllegalArgumentException("目录不存在：" + dir.getAbsolutePath());
        if (!dir.isDirectory()) throw new IllegalArgumentException("不是目录：" + dir.getAbsolutePath());
    }

    /**
     * 过滤非重复组（仅保留2个以上文件的组）
     */
    private void filterNonDuplicateGroups(Map<String, List<File>> resultMap) {
        Iterator<Map.Entry<String, List<File>>> iterator = resultMap.entrySet().iterator();
        while (iterator.hasNext()) {
            if (iterator.next().getValue().size() <= 1) {
                iterator.remove();
            }
        }
    }

    /**
     * 对文件路径列表按字典序排序
     */
    private void sortFilePaths(Map<String, List<File>> resultMap) {
        resultMap.forEach((hash, paths) -> Collections.sort(paths));
    }

    /**
     * 字节数组转16进制字符串
     */
    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

}
